<?php
/**
 * 定义 View_Abstract 类
 * 
 * @copyright Copyright (c) 2008 Classic Reappear Technology LLC
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
 * @version $Id: abstract.php 2009-2-27 下午03:03:30 蓝色身份 $
 */

/**
 * 视图抽象类
 *
 * @author 蓝色身份 <lance.w@live.cn>
 * @copyright Copyright (c) 2008 Classic Reappear Technology LLC
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
 * @package View
 */
abstract class View_Abstract implements View_Interface
{
    /**
     * 视图文件的存放路径
     *
     * @var array
     */
    private $_scriptPath = array();

    /**
     * 视图助手文件的存放路径
     *
     * @var array
     */
    private $_helperPath = array();

    /**
     * 视图助手对象实例
     *
     * @var array
     */
    private $_helper = array();

    /**
     * 已载入的视图助手
     *
     * @var array
     */
    private $_helperLoaded = array();

    /**
     * 用来转义特殊字符的函数
     *
     * @var string
     */
    private $_escape = 'htmlspecialchars';

    /**
     * 存储文件编码
	 *
     * @var string
     */
    private $_encoding = 'utf-8';

    /**
     * 指定某些未初始化的视图变量被访问时是否发出警告
     *
     * @var boolean
     */
    private $_strictVars = false;

    /**
     * 构造函数
     *
     * @param array|Config $config
     */
    public function __construct($config = array())
    {
        if ($config instanceof Config) {
			$config = $config->toArray();
		}

        $this->setScriptPath(null);
        $this->setHelperPath(null);

        if (array_key_exists('escape', $config)) {
            $this->setEscape($config['escape']);
        }

        if (array_key_exists('encoding', $config)) {
            $this->setEncoding($config['encoding']);
        }

        if (array_key_exists('script_path', $config)) {
            $this->addScriptPath($config['script_path']);
        }

		if (array_key_exists('helper_path', $config)) {
            $prefix = 'View_Helper';
            if (array_key_exists('helper_path_prefix', $config)) {
                $prefix = $config['helper_path_prefix'];
            }
            $this->addHelperPath($config['helper_path'], $prefix);
        }

        if (array_key_exists('strict_vars', $config)) {
            $this->strictVars($config['strict_vars']);
        }

        $this->init();
    }

    /**
     * 返回视图对象
     *
     * @return View_Abstract
     */
    public function getEngine()
    {
        return $this;
    }

    /**
     * 初始化函数
     *
     * @return void
     */
    public function init()
    {
    }

    /**
     * 访问视图变量时则触发该函数
     *
     * @param string $key 变量名
     * @return null
     */
    public function __get($key)
    {
        if ($this->_strictVars) {
            trigger_error('Key "' . $key . '" does not exist', E_USER_NOTICE);
        }

        return null;
    }

    /**
     * 魔术方法判断变量是否已指派
     *
     * @param string $key 变量名
     * @return boolean
     */
    public function __isset($key)
    {
        if ('_' != substr($key, 0, 1)) {
            return isset($this->$key);
        }

        return false;
    }

    /**
     * 魔术方法指派变量
     *
     * @param string $key 变量名
     * @param mixed $val 变量值
     * @return void
     */
    public function __set($key, $val)
    {
        if ('_' != substr($key, 0, 1)) {
            $this->$key = $val;
            return;
        }

        throw new Exception('Setting private or protected class members is not allowed', $this);
    }

    /**
     * 魔术方法删除一个已指派的变量
     *
     * @param string $key 变量名
     * @return void
     */
    public function __unset($key)
    {
        if ('_' != substr($key, 0, 1) && isset($this->$key)) {
            unset($this->$key);
        }
    }

    /**
     * 当被执行的函数不存在时则自动调用该函数
     *
     * @param string $name
     * @param array $args
     * @return string
     */
    public function __call($name, $args)
    {
        $helper = $this->getHelper($name);

		return call_user_func_array(array($helper, $name), $args);
    }

    /**
     * 添加视图文件存放的路径
     *
     * @param string|array 视图文件存放的路径
     * @return View_Abstract
     */
    public function addScriptPath($path)
    {
		foreach ((array) $path as $dir) {
            $dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $dir);
            $dir = rtrim($dir, DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
			if (is_dir($dir)) {
				array_unshift($this->_scriptPath, $dir);
			}
        }
        return $this;
    }

    /**
     * 设置视图文件存放的路径
     *
     * @param string|array 视图文件存放的路径
     * @return View_Abstract
     */
    public function setScriptPath($path)
    {
        $this->_scriptPath = array();
        $this->addScriptPath($path);
        return $this;
    }

    /**
     * 获取视图存放的路径
     *
     * @param string $name 视图文件名
     * @return false|string
     */
    public function getScriptPath($name)
    {
        try {
            $path = $this->_script($name);
            return $path;
        } catch (Exception $e) {
            if (strstr($e->getMessage(), 'no view script directory set')) {
                throw $e;
            }

            return false;
        }
    }

    /**
     * 获取所有视图文件存放路径
     *
     * @return array
     */
    public function getScriptPaths()
    {
        return $this->_scriptPath;
    }

    /**
     * 附加一个额外的路径用于存放视图助手文件
     *
     * @param string|array 视图助手文件存放的路径
     * @param string $classPrefix
     * @return View_Abstract
     */
    public function addHelperPath($path, $classPrefix = 'View_Helper_')
    {
        if (!empty($classPrefix) && ('_' != substr($classPrefix, -1))) {
            $classPrefix .= '_';
        }
		foreach ((array) $path as $dir) {
            $dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $dir);
            $dir = rtrim($dir, DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
			if (is_dir($dir)) {
				array_unshift($this->_helperPath, array('prefix' => $classPrefix, 'dir' => $dir));
			}
        }
        return $this;
    }

    /**
     * 设置视图助手文件存放的路径
     *
     * @param string|array $path 视图助手文件存放的路径
     * @return View_Abstract
     */
    public function setHelperPath($path, $classPrefix = 'View_Helper_')
    {
		$dir = DIRECTORY_SEPARATOR . 'helper' . DIRECTORY_SEPARATOR;
		$this->_helperPath = array(array(
			'prefix' => 'View_Helper_',
			'dir' => dirname(__FILE__) . $dir
		));
        $this->addHelperPath($path, $classPrefix);
        return $this;
    }

    /**
     * 获取所有视图助手文件存放的路径
     *
     * @return array
     */
    public function getHelperPaths()
    {
        return $this->_helperPath;
    }

    /**
     * 获取并实例化视图助手
     *
     * @param  string $name
     * @return object
     */
    public function getHelper($name)
    {
        $name = ucfirst($name);
        if (!isset($this->_helper[$name])) {
            $class = $this->_loadHelper($name);
            $this->_helper[$name] = new $class();
            if (method_exists($this->_helper[$name], 'setView')) {
                $this->_helper[$name]->setView($this);
            }
        }

        return $this->_helper[$name];
    }

    /**
     * 获取所有有效的视图助手实例
     *
     * @return array
     */
    public function getHelpers()
    {
        return $this->_helper;
    }

    /**
     * 设置 _escape() 回调函数
     *
     * @param mixed $spec
     * @return View_Abstract
     */
    public function setEscape($spec)
    {
        $this->_escape = $spec;
        return $this;
    }

    /**
     * 指派视图变量
     *
     * @see __set()
     * @param string|array $spec 变量名
     * @param mixed $value 变量值
     * @return View_Abstract
     */
    public function assign($spec, $value = null)
    {
        if (is_string($spec)) {
            if ('_' == substr($spec, 0, 1)) {
                throw new Exception('Setting private or protected class members is not allowed', $this);
            }
            $this->$spec = $value;
        } elseif (is_array($spec)) {
            $error = false;
            foreach ($spec as $key => $val) {
                if ('_' == substr($key, 0, 1)) {
                    $error = true;
                    break;
                }
                $this->$key = $val;
            }
            if ($error) {
                throw new Exception('Setting private or protected class members is not allowed', $this);
            }
        } else {
            throw new Exception('assign() expects a string or array, received ' . gettype($spec), $this);
        }

        return $this;
    }

    /**
     * 返回所有已赋值的变量
     *
     * @return array
     */
    public function getVars()
    {
        $vars   = get_object_vars($this);
        foreach ($vars as $key => $value) {
            if ('_' == substr($key, 0, 1)) {
                unset($vars[$key]);
            }
        }

        return $vars;
    }

    /**
     * 清除所有已赋值的变量
     *
     * @return void
     */
    public function clearVars()
    {
        $vars   = get_object_vars($this);
        foreach ($vars as $key => $value) {
            if ('_' != substr($key, 0, 1)) {
                unset($this->$key);
            }
        }
    }

    /**
     * 渲染视图以便输出到屏幕
     *
     * @param string $name 视图文件
     * @return string
     */
    public function render($name)
    {
        $filename = $this->_script($name);
        unset($name);

        ob_start();
        $this->_run($filename);

        return ob_get_clean();
    }

    /**
     * 转义特殊字符
     *
     * @param mixed $var
     * @return mixed
     */
    public function escape($var)
    {
        if (in_array($this->_escape, array('htmlspecialchars', 'htmlentities'))) {
            return call_user_func($this->_escape, $var, ENT_COMPAT, $this->_encoding);
        }

        return call_user_func($this->_escape, $var);
    }

    /**
     * 设置文件编码
     *
     * @param string $encoding
     * @return View_Abstract
     */
    public function setEncoding($encoding)
    {
        $this->_encoding = $encoding;
        return $this;
    }

    /**
     * 返回当前文件编码
     *
     * @return string
     */
    public function getEncoding()
    {
        return $this->_encoding;
    }

    /**
     * 指定某些未初始化的视图变量被访问时是否发出警告
     *
     * @param boolean $flag
     * @return View_Abstract
     */
    public function strictVars($flag = true)
    {
        $this->_strictVars = ($flag) ? true : false;

        return $this;
    }

    /**
     * 从已加载的路径查找视图文件
     *
     * @param $name string
     * @return void
     */
    protected function _script($name)
    {
        if (0 == count($this->_scriptPath)) {
            throw new Exception('no view script directory set; unable to determine location for view script',
                $this);
        }

        foreach ($this->_scriptPath as $dir) {
            if (is_readable($dir . $name)) {
                return $dir . $name;
            }
        }

        $message = "script '$name' not found in path (" . implode(PATH_SEPARATOR, $this->_scriptPath) . ")";
        throw new Exception($message, $this);
    }

    /**
     * 载入视图助手类文件
     *
     * @param  string $name
     * @return string
     * @throws View_Exception
     */
    private function _loadHelper($name)
    {
        if (isset($this->_helperLoaded[$name])) {
            return $this->_helperLoaded[$name];
        }

        $file = ucfirst($name) . '.php';

        foreach ($this->_helperPath as $info) {
            $dir    = $info['dir'];
            $prefix = $info['prefix'];

            $class = $prefix . ucfirst($name);

            if (class_exists($class, false)) {
                $reflection = new ReflectionClass($class);
                $this->_helperLoaded[$name] = $class;
                return $class;
            } elseif (is_file($dir . $file)) {
                include_once $dir . $file;

                if (class_exists($class, false)) {
                    $this->_helperLoaded[$name] = $class;
                    return $class;
                }
            }
        }

        throw new Exception("helper '$name' not found in path", $this);
    }

    /**
     * 包含并运行指定视图文件
     *
     * @return mixed
     */
    abstract protected function _run();
}